package com.owen.he.jee.dao.impl;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.Table;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.orm.hibernate4.HibernateCallback;
import org.springframework.orm.hibernate4.HibernateTemplate;

import com.owen.he.jee.vo.LoadmoreParam;
import com.owen.he.jee.vo.LoadmoreResult;
import com.owen.he.jee.vo.PagingResult;

public final class HibernateDao<T, PK extends Serializable> {
	protected HibernateTemplate hibernateTemplate;
	
	@Resource(name = "sessionFactory")
	public void setHibernateTemplate(SessionFactory sessionFactory) {
		this.hibernateTemplate = new HibernateTemplate(sessionFactory);
	}
	
	/**
	 * 保存或修改非持久化实例，持久化对象也可调用该方法进行更新，但推荐使用flush方法
	 * @param t
	 * @see HibernateDao#flush()
	 */
	public void saveOrUpdate(T t) {
		this.hibernateTemplate.saveOrUpdate(t);
	}
	
	@SuppressWarnings("hiding")
	public <T> void saveUpdate(T entity) {
		try {
			hibernateTemplate.saveOrUpdate(entity);
			hibernateTemplate.flush();
		} catch (RuntimeException e) {
			throw e;
		}
	}
	
	/**
	 * 批量更新hql
	 * @param hql
	 * @param params
	 * @return
	 */
	public int update(String hql, Object... params) {
		return this.hibernateTemplate.bulkUpdate(hql, params);
	}
	
	public static class Parameter {
		private String name;
		private Object value;
		
		public Parameter() {}
		
		public Parameter(String name, Object value) {
			this.name = name;
			this.value = value;
		}

		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Object getValue() {
			return value;
		}
		public void setValue(Object value) {
			this.value = value;
		}
	}
	
	public int update(final String hql, final Parameter... params) {
		return this.hibernateTemplate.execute(new HibernateCallback<Integer>() {

			@Override
			public Integer doInHibernate(Session session) throws HibernateException {
				final Query query = session.createQuery(hql);
				if (ArrayUtils.isNotEmpty(params)) {
					for (Parameter parameter : params) {
						query.setParameter(parameter.getName(), parameter.getValue());
					}
				}
				return query.executeUpdate();
			}
		});
	}
	
	/**
	 * 将实例持久化，即保存到数据库，并返回新建数据库记录id
	 * @param object
	 * @return 数据库创建该对应记录对应主键值
	 */
	@SuppressWarnings("unchecked")
	public PK save(T object) {
		return (PK) this.hibernateTemplate.save(object);
	}
	
	/**
	 * 将实例持久化，并返回持久化后实例
	 * @param object 待持久化对象
	 * @return 持久化后实例
	 */
	@SuppressWarnings("unchecked")
	public T insert(T object) {
		final Serializable id = this.hibernateTemplate.save(object);
		return (T) this.hibernateTemplate.get(object.getClass(), id);
	}
	
	/**
	 * 返回Hql查询结果的第一条记录，未查询到数据则返回null
	 * @param hql Hql查询语句，查询条件使用?代替，再依次在params中传递
	 * @param params hql查询条件集合
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T findSingleItem(String hql, Object... params) {
		final List<?> find = this.hibernateTemplate.find(hql, params);
		if (find != null && find.size() > 0) {
			return (T) find.get(0);
		} else {
			return null;
		}
	}
	
	/**
	 * 返回Hql查询对应结果
	 * @param hql Hql查询语句，查询条件使用?代替
	 * @param params Hql查询条件集合，顺序与Hql中?出现顺序一致
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> find(String hql, Object... params) {
		return (List<T>) this.hibernateTemplate.find(hql, params);
	}
	
	/**
	 * 查找类表
	 * @param hql
	 * @param params
	 * @return
	 */
	public List<?> findList(String hql, Object... params) {
		return (List<?>) this.hibernateTemplate.find(hql, params);
	}
	
	/**
	 * 获取主键对应实例，未找到对应时返回null
	 * @param id 实例主键
	 * @return
	 */
	public T get(PK id, Class<T> type) {
		return this.hibernateTemplate.get(type, id);
	}
	
	/**
	 * 获取主键对应实例
	 * @param entityName 实体名字  id 实例主键
	 * @return
	 */
	@SuppressWarnings({ "unchecked", "rawtypes", "hiding" })
	public <T> T getEntity(Class entityName, Serializable id) {

		T t = (T) hibernateTemplate.get(entityName, id);
		if (t != null) {
			hibernateTemplate.flush();
		}
		return t;
	}
	
	/**
	 * 更新实例，传入的参数应当时持久化过的游离态实例，并包含主键信息
	 * @param t
	 */
	public void update(T t) {
		this.hibernateTemplate.update(t);
	}
	
	/**
	 * 返回表名
	 * @return
	 */
	final public String tableName(Class<T> type) {
		return type.getSimpleName();
	}
	
	public String realTableName(Class<T> type) {
		final Table table = AnnotationUtils.findAnnotation(type, Table.class);
		if (table != null) {
			return table.name();
		} else {
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * 删除实例
	 * @param entity
	 */
	public void delete(T entity) {
		if (entity != null) {
			this.hibernateTemplate.delete(entity);
			this.hibernateTemplate.flush();
		}
	}
	
	/**
	 * 获取当前实例类型对应的查询DetachedCriteria
	 * @return
	 */
	public DetachedCriteria detachedCriteria(Class<T> type) {
		return DetachedCriteria.forClass(type);
	}
	
	/**
	 * 返回DetachedCriteria查询结果
	 * @param criteria
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(DetachedCriteria criteria) {
		return (List<T>) this.hibernateTemplate.findByCriteria(criteria);
	}
	
	/**
	 * 返回DetachedCriteria查询第一条记录，如果未查询到结果则返回null
	 * @param criteria
	 * @return
	 */
	public T findFirstByCriteria(DetachedCriteria criteria) {
		final List<T> list = findByCriteria(criteria);
		return first(list);
	}

	public <O> O first(final List<O> list) {
		return CollectionUtils.isNotEmpty(list) ? list.get(0) : null;
	}
	
	public void flush() {
		this.hibernateTemplate.flush();
	}
	
	public void clear() {
		this.hibernateTemplate.clear();
	}
	
	/**
	 * 当前实例的分页查询，实现参数中的PagingCallback回调接口，实现数量查询各列表查询条件设置
	 * @param bean 分页数据
	 * @param callback 分页查询条件设置回调接口
	 * @return
	 */
	public LoadmoreResult<T> loadmore(LoadmoreParam bean, final LoadmoreCallback callback, final Class<T> type) {
		final LoadmoreResult<T> pageList = new LoadmoreResult<T>();
		final int pageSize = bean.getPageSize();
		final int currentPage = bean.getCurrentPage();

		final List<T> entities = hibernateTemplate.execute(new HibernateCallback<List<T>>() {
			@SuppressWarnings("unchecked")
			public List<T> doInHibernate(Session session) throws HibernateException {
				final Criteria criteria = session.createCriteria(type);
				if (callback != null) {
					callback.doWithListCriteria(criteria);
				}
				criteria.setFirstResult(firstResult(pageSize, currentPage));
				criteria.setMaxResults(pageSize);
				return criteria.list();
			}
		});
		pageList.setEntities(entities);
		pageList.setCurrentPage(currentPage);
		pageList.setPageSize(pageSize);
		return pageList;
	}
	
	public PagingResult<T> pagings(LoadmoreParam bean, final PagingCallback callback, final Class<T> type) {
		final PagingResult<T> pageList = new PagingResult<T>();
		final int pageSize = bean.getPageSize();
		final int currentPage = bean.getCurrentPage();

		final int totalSize = hibernateTemplate.execute(new HibernateCallback<Integer>() {
			public Integer doInHibernate(Session session) throws HibernateException {
				final Criteria criteria = session.createCriteria(type);
				if (callback != null) {
					callback.doWithCountCriteria(criteria);
				}
				criteria.setProjection(Projections.rowCount());
				return ((Number) criteria.uniqueResult()).intValue();
			}
		});

		pageList.setTotal(totalSize);
		pageList.setTotalPage(totalPage(totalSize, pageSize));
		
		if (totalSize > 0) {
			final List<T> entities = hibernateTemplate.execute(new HibernateCallback<List<T>>() {
				@SuppressWarnings("unchecked")
				public List<T> doInHibernate(Session session) throws HibernateException {
					final Criteria criteria = session.createCriteria(type);
					if (callback != null) {
						callback.doWithListCriteria(criteria);
					}
					criteria.setFirstResult(firstResult(pageSize, currentPage));
					criteria.setMaxResults(pageSize);
					return criteria.list();
				}
			});
			pageList.setEntities(entities);
			pageList.setCurrentPage(currentPage);
			pageList.setPageSize(pageSize);
		}
		return pageList;
	}
	
	private int totalPage(int totalSize, int pageSize) {
		if (totalSize == 0 || pageSize == 0) {
			return 0;
		}
		final int tmp = (int) (totalSize / pageSize);
		return (tmp * pageSize) < totalSize ? tmp+1 : tmp;
	}
	
	public int firstResult(int pageSize, int currentPage) {
		return Math.max(pageSize * (currentPage-1), 0);
	}
	
	/**
	 * 分页查询条件设置回调接口
	 * @author tanghuabo
	 */
	interface LoadmoreCallback {
		
		/**
		 * 设置数量结果查询条件，该方法应当设置OrderBy字段，以确保结果有序
		 * @param criteria
		 */
		public void doWithListCriteria(Criteria criteria);
	}
	
	public interface PagingCallback extends LoadmoreCallback {
		public void doWithCountCriteria(Criteria criteria);
	}

	public void flushAndClear() {
		this.hibernateTemplate.flush();
		this.hibernateTemplate.clear();
	}
	
	/**
	 * 查询并返回数量，如查询结果不是数字时，返回0
	 * @param hql
	 * @param params
	 * @return
	 */
	public int findForInt(String hql, Object... params) {
		final List<?> objs = this.hibernateTemplate.find(hql, params);
		if (CollectionUtils.isNotEmpty(objs)) {
			final Object first = objs.get(0);
			if (first instanceof Number) {
				return ((Number) first).intValue();
			}
		}
		return 0;
	}
	
	/**
	 * 查询数字返回结果
	 * @param criteria
	 * @return
	 * @author bobo
	 */
	public int findForInt(DetachedCriteria criteria) {
		final List<?> results = this.hibernateTemplate.findByCriteria(criteria);
		if (CollectionUtils.isNotEmpty(results) && results.size()==1) {
			final Object result = results.get(0);
			if (result instanceof Number) {
				return ((Number) result).intValue();
			}
		}
		return 0;
	}
	
	/**
	 * 执行更新、删除sql
	 * @param sql
	 * @param parameters
	 * @return
	 * @author bobo
	 */
	public int executeSql(final String sql, final Parameter... parameters) {
		return this.hibernateTemplate.executeWithNativeSession(new HibernateCallback<Integer>() {
			@Override
			public Integer doInHibernate(Session session) throws HibernateException {
				final SQLQuery query = session.createSQLQuery(sql);
				if (ArrayUtils.isNotEmpty(parameters)) {
					for (Parameter parameter : parameters) {
						query.setParameter(parameter.getName(), parameter.getValue());
					}
				}
				return query.executeUpdate();
			}
		});
	}
	
	/**
	 * Load datas in batch, prevent load more datas in once and cause out of memory.
	 * The `remote` method of Iterator is unsupported.
	 * @param maker
	 * @return
	 * @author bobo
	 */
	public Iterator<T> findInBatch(BatchIteratorCriteriaMaker maker) {
		return new BatchIterator<T>(maker, this.hibernateTemplate);
	}

	public static class BatchIterator<R> implements Iterator<R> {
		private BatchIteratorCriteriaMaker maker;
		private Iterator<R> iterator;
		private int firstResult = 0;
		private int maxResults = 500;
		private HibernateTemplate hibernateTemplate;
		
		BatchIterator(BatchIteratorCriteriaMaker maker, HibernateTemplate hibernateTemplate) {
			this.hibernateTemplate = hibernateTemplate;
			this.maker = maker;
		}

		@SuppressWarnings("unchecked")
		@Override
		public boolean hasNext() {
			if (iterator==null || !iterator.hasNext()) {
				final DetachedCriteria criteria = maker.make();
				final List<R> findByCriteria = (List<R>) hibernateTemplate.findByCriteria(criteria,
						firstResult, maxResults);
				if (CollectionUtils.isNotEmpty(findByCriteria)) {
					firstResult += maxResults;
					this.iterator = findByCriteria.iterator();
					return true;
				} else {
					return false;
				}
			} else {
				return true;
			}
		}

		@Override
		public R next() {
			return iterator.next();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		} 
	}
	
	/**
	 * 构造查询条件的回调接口
	 * @author tanghuabo
	 */
	public interface BatchIteratorCriteriaMaker {
		DetachedCriteria make();
	}

	public BatchIteratorWithTotalCount<T> findInBatch(BatchIteratorCriteriaWithCountMaker maker) {
		return new BatchIteratorWithTotalCount<T>(maker, this.hibernateTemplate);
	}
	
	public interface BatchIteratorCriteriaWithCountMaker extends BatchIteratorCriteriaMaker{
		DetachedCriteria makeCount();
	}
	
	public static class BatchIteratorWithTotalCount<T> {
		private BatchIteratorCriteriaWithCountMaker maker;
		private HibernateTemplate hibernateTemplate;
		
		private int totalCount;
		private boolean hasGetTotalCount = false;
		
		public BatchIteratorWithTotalCount(BatchIteratorCriteriaWithCountMaker maker,
				HibernateTemplate hibernateTemplate) {
			this.maker = maker;
			this.hibernateTemplate = hibernateTemplate;
		}
		
		public BatchIteratorWithTotalCount() {}
		
		public int getTotalCount() {
			final int result = hasGetTotalCount ? this.totalCount : getTotalCountFromDb();
			hasGetTotalCount = true;
			return result;
		}

		private int getTotalCountFromDb() {
			return hibernateTemplate.execute(new HibernateCallback<Integer>() {
				public Integer doInHibernate(Session session) throws HibernateException {
					final DetachedCriteria detachedCriteria = maker.makeCount();
					detachedCriteria.setProjection(Projections.rowCount());
					return ((Long)hibernateTemplate.findByCriteria(detachedCriteria).get(0)).intValue();
				}    
			});
		}
		public BatchIterator<T> getBatchIterator() {
			return new BatchIterator<T>(maker, this.hibernateTemplate);
		}
	}
}
